package com.gitee.melin.bee.core.grpc.error;

import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.protocol.grpc.interceptors.ServerInterceptor;

import java.net.SocketAddress;

import static io.grpc.Grpc.TRANSPORT_ATTR_REMOTE_ADDR;
import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER;

@Activate(group = {PROVIDER}, order = 4900)
public class GrpcExceptionInterceptor implements ServerInterceptor {

    private GrpcExceptionResponseHandler grpcAdviceExceptionHandler;

    /**
     * inject spring bean
     * @param grpcAdviceExceptionHandler
     */
    public void setGrpcAdviceExceptionHandler(GrpcExceptionResponseHandler grpcAdviceExceptionHandler) {
        this.grpcAdviceExceptionHandler = grpcAdviceExceptionHandler;
    }

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
            final ServerCall<ReqT, RespT> call,
            final Metadata headers,
            final ServerCallHandler<ReqT, RespT> next) {

        try {
            SocketAddress socketAddress = call.getAttributes().get(TRANSPORT_ATTR_REMOTE_ADDR);

            final GrpcExceptionServerCall<ReqT, RespT> handledCall =
                    new GrpcExceptionServerCall<>(call, this.grpcAdviceExceptionHandler);
            final ServerCall.Listener<ReqT> delegate = next.startCall(handledCall, headers);
            return new GrpcExceptionListener<>(delegate, call, this.grpcAdviceExceptionHandler, socketAddress);

        } catch (final Throwable error) {
            // For errors from grpc method implementation methods directly (Not via StreamObserver)
            this.grpcAdviceExceptionHandler.handleError(call, error); // Required to close streaming calls
            return noOpCallListener();
        }
    }

    /**
     * Creates a new no-op call listener because you can neither return null nor throw an exception in
     * {@link #interceptCall(ServerCall, Metadata, ServerCallHandler)}.
     *
     * @param <ReqT> The type of the request.
     * @return The newly created dummy listener.
     */
    protected <ReqT> ServerCall.Listener<ReqT> noOpCallListener() {
        return new ServerCall.Listener<ReqT>() {};
    }

}
